package org.mbds.wolf.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import android.util.Log;

/**
 * This class is intended for HTTP requests
 * @author Anne-Marie Lesas
 */

/*
 * HTTP request is performed outside of the UI main thread 
 * So the THHP access is made in a thread
 */
public class HttpRequest implements Runnable {
	
	public final static String TAG = "HttpRequest";
	public final static String ENCODING = "UTF-8";
	public final static BasicHttpContext localContext = new BasicHttpContext();
	
	/**
	 * HTTP Methods
	 * @return enumeration
	 */
	public static enum Method {
		POST, GET, PUT, DELETE
	}
	
	/*
	 * Expected httpFormat 
	 * Note: care to be sure the httpFormat is available
	 * (could be text/HTML, JSON or XML...)
	 */
	public final static String APPLICATION_XML = "application/xml";
	public final static String APPLICATION_JSON = "application/json";
	public final static String TEXT_HTML = "text/html";

	/*
	 * Lock to wait for server response
	 */
	private final static Object lock = new Object();
	
	/*
	 * Server URL
	 */
	private static String hostUrl = null;
	/*
	 * Server port
	 */
	private static int hostPort = 80;
	/*
	 * Web service path
	 */
	private static String path = "";
	/*
	 * Web service parameters
	 */
	private static String params = "";
	
	/*
	 * HttpFormat for the returned response
	 */
	private static HttpFormat httpFormat = HttpFormat.TEXT_HTML;
	
	/*
	 * Web service required Method 
	 */
	private static Method method = Method.GET;

	/*
	 * Response received from the server
	 */
	private static String httpResponse = "";
	
	/*
	 * Entity object if needed for put/post methods (JSON, XML)
	 */
	private static String entity = "";
	
	/**
	 * Singleton
	 */
	private static HttpRequest _instance = new HttpRequest();
	public static HttpRequest getInstance() {return _instance;};
	private HttpRequest() {};

	/**
	 * isReachable
	 * @return boolean
	 */
	public static boolean isReachable(String hostUrl, int hostPort) {
		if (hostUrl==null)return false;
		Log.d(TAG, "Trying to reach: " + hostUrl);
		Socket bound = null;
		try {
			InetAddress address = InetAddress.getByName(hostUrl);
			bound = new Socket(address, hostPort);
		} catch (UnknownHostException e) {
			Log.d(TAG, "UnknownHostException: " + e.getMessage());
		} catch (IOException e) {
			Log.d(TAG, "IOException: " + e.getMessage());
		}
		if (bound != null && bound.isConnected()) {
			try {
				bound.close();
			} catch (IOException e) {
				//Log.d(TAG, "IOException: " + e.getMessage());
			}
			return true;
		}
		return false;
	}	
	
	/**
	 * Query
	 * @param hostUrl
	 * @param hostPort
	 * @param method
	 * @param httpFormat
	 * @param path
	 * @param params
	 * @param entity
	 * @return response String or null
	 */
	public synchronized String query(String hostUrl, int hostPort, Method method, 
			HttpFormat httpFormat, String path, String params, String entity) {
		
		// Set static parameters
		setHostPort(hostPort);
		setHostUrl(hostUrl);
		setMethod(method);
		setHttpFormat(httpFormat);
		setPath(path);
		setParams(params);
		setEntity(entity);
		
		// Run the request into a thread
		new Thread(this).start();

		// Wait the end of thread
		try {
        synchronized (lock) {             
        	lock.wait();         
        	} 			
	    } catch (InterruptedException e) {
		} 
		// return the String response
		return httpResponse;
	}

	/**
	 * HttpRequest in a thread required since API 16
	 * uses static variables
	 */
	public void run() {
		httpResponse=query(hostUrl, hostPort, path, params, method, httpFormat, entity);
		// Notify unlock waiting processes
        synchronized (lock) {             
        	lock.notify();         
        } 			
	}
	
	/**
	 * Query
	 * @param path
	 * @param params
	 * @param method
	 * @param accept
	 * @param entity
	 * @return String response
	 */
	private static String query(final String hostUrl, final int hostPort, String path, String params, 
			Method method, HttpFormat accept, String entity) {
		
		if (!isReachable(hostUrl, hostPort)) return null;
		if (method==null) method=Method.GET;
		if (accept==null) accept=HttpFormat.TEXT_HTML;
		if (path==null) path="";
		if (params==null) params="";
		if (entity==null) entity="";
		
		URI uri;
		try {
			uri = new URI(hostUrl
					+ ":"
					+ hostPort
					+ path
					+ params.replace(" ", "%20").replace((char) 42 + "", "%22")
					.replace("'", "%27"));
		} catch (URISyntaxException e) {
			Log.e(TAG, "Malformed URL", e);
			return null;
		}
		Log.d(TAG, "URL: " + uri.toString());
		System.out.println("URL: " + uri.toString());

		DefaultHttpClient client = new DefaultHttpClient();

		HttpResponse response = null;
		StringEntity object = null;
		try {
			if (method == Method.POST) {
				HttpPost http = new HttpPost(uri);
				http.setHeader("Content-Type",
						"application/x-www-form-urlencoded");
				if (entity != null) {
					object = new StringEntity(entity, "UTF-8");
					object.setContentType(new BasicHeader(HTTP.CONTENT_TYPE,
							accept.toString()));
					http.setEntity(object);
				}
				http.addHeader("Content-Type", accept.toString());
				http.addHeader("Accept", accept.toString());
				response = client.execute(http, localContext);
			} else if (method == Method.PUT) {
				HttpPut http = new HttpPut(uri);
				if (entity != null) {
					object = new StringEntity(entity, "UTF-8");
					object.setContentType(accept.toString());
					http.setEntity(object);
				}
				http.addHeader("Content-Type", accept.toString());
				http.addHeader("Accept", accept.toString());
				response = client.execute(http, localContext);
			} else if (method == Method.DELETE) {
				HttpDelete http = new HttpDelete(uri);
				http.setHeader("Content-Type",
						"application/x-www-form-urlencoded");
				http.addHeader("Accept", accept.toString());
				http.addHeader("Accept-Charset", "utf-8");
				response = client.execute(http, localContext);
			} else {
				HttpGet http = new HttpGet(uri);
				http.addHeader("Accept", accept.toString());
				http.addHeader("Accept-Charset", "utf-8");
				response = client.execute(http, localContext);
			}
		} catch (ClientProtocolException e) {
			Log.e(TAG, "ClientProtocol Error", e);
			return null;
		} catch (IOException e) {
			Log.e(TAG, "IO Error", e);
			return null;
		} catch (Exception e) {
			Log.e(TAG, "Error", e);
			return null;
		}

		if (response == null || response.getEntity() == null) {
			return null;
		}

		HttpEntity entityObject = response.getEntity();

		Log.d(TAG, "Response Entity: " + entityObject.getContentLength() + " bytes, "
				+ entityObject.getContentType() + "; " + entityObject.getContentEncoding()
				+ " " + EntityUtils.getContentCharSet(entityObject));

		String body = null;

		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			entityObject.writeTo(out);
		} catch (IOException e) {
			Log.e(TAG, "IO Error", e);
			return null;
		}

		try {
			body = out.toString(ENCODING);
		} catch (UnsupportedEncodingException e) {
			Log.e(TAG, "Unsupported Encoding Error", e);
			return null;
		}

		Log.d(TAG, "Response: " + body);

		if (body == null || body.length() == 0) {
			return null;
		}
		// String returned
		return body;
	}
	
	/**
	 * getHostUrl
	 * @return the hostUrl
	 */
	public static String getHostUrl() {
		return hostUrl;
	}
	/**
	 * setHostUrl
	 * @param hostUrl the hostUrl to set
	 */
	private static void setHostUrl(String hostUrl) {
		HttpRequest.hostUrl = hostUrl;
	}
	/**
	 * getHostPort
	 * @return the hostPort
	 */
	public static int getHostPort() {
		return hostPort;
	}
	/**
	 * setHostPort
	 * @param hostPort the hostPort to set
	 */
	private static void setHostPort(int hostPort) {
		HttpRequest.hostPort = hostPort;
	}
	/**
	 * SetEntity
	 * @param entity
	 */
	private static void setEntity(String entity) {
		HttpRequest.entity = entity;
	}
	/**
	 * setParams
	 * @param params
	 */
	private static void setParams(String params) {
		HttpRequest.params=params;
	}
	/**
	 * setPath
	 * @param path
	 */
	private static void setPath(String path) {
		HttpRequest.path= path;
	}
	/**
	 * setHttpFormat
	 * @param httpFormat
	 */
	private static void setHttpFormat(HttpFormat httpFormat) {
		HttpRequest.httpFormat = httpFormat;
	}
	/**
	 * setMethod
	 * @param method
	 */
	private static void setMethod(Method method) {
		HttpRequest.method = method;
	}
}
